﻿using System;
using System.Collections.Generic;
using ActionLogic.Impl;
using BlockLogic.Impl.Blocks.ServiceDevice.Enum;
using BlockLogic.Interface;
using BlockLogic.Interface.Blocks.ServiceDevice;
using ComputationalCore.Interface.RandomGenerator;
using BlockLogic.Impl.Enum;
using ComputationalCore.Helper;

namespace BlockLogic.Impl.Blocks.ServiceDevice
{
    public abstract class ServiceDevice: Block, IServiceDevice
    {
        private IRequest _servedRequest;

        public int MaxQueueLength { get; set; }
        public IRandomGenerator RandomGenerator { get; set; }
        public ServiceDiscipline ServiceDiscipline { get; set; }

        public double WorkingTime { get; private set; }
        public double DownTime { get; private set; }
        public List<IRequest> RequestsQueue { get; private set; }
        
        public IRequestBuffer InputBuffer { get; private set; }
        public IRequestBuffer OutputBuffer { get; set; }
        public IRequestBuffer CheckerBuffer { get; set; }
        public IRequestBuffer UnServedBuffer { get; set; }
        
        protected ServiceDevice(Guid id, BlockType type, int maxQueueLength) : base(id, type)
        {
            RequestsQueue = new List<IRequest>();
            InputBuffer = new RequestBuffer(ID);
            UnServedBuffer = new RequestBuffer(ID);
            ServiceDiscipline = ServiceDiscipline.LIFO;
            MaxQueueLength = maxQueueLength;
            WorkingTime = 0;
            DownTime = 0;
        }
        
        protected void ServiceRequests()
        {
            if (RequestsQueue.Count != 0)
            {
                var servicedTime = RandomGenerator.GetNext();
                _servedRequest = Dequeue();
                _servedRequest.ServiceDeviceID = ID;
                _servedRequest.ServiceTime = servicedTime;

                DownTime = _servedRequest.InitTime + _servedRequest.WaitingTime - WorkingTime;
                WorkingTime += servicedTime;

                IncWaitingTimeInQueue(servicedTime);
                
                PutServed(_servedRequest);

                var executor = ActionExecutor.GetInstance();
                executor.Add(WorkingTime + DownTime, Service);
            }
            else
            {
                InputBuffer.OnRequestAdded += CheckBuffer;
            }
        }

        private void IncWaitingTimeInQueue(double serviceTime)
        {
            int i = 0;
            while (i < RequestsQueue.Count)
            {
                var req = RequestsQueue[i++];
                if (-1 != req.MaxWaitingTime && req.WaitingTime > req.MaxWaitingTime)
                {
                    UnServedBuffer.Add(req);
                    RequestsQueue.Remove(req);
                    --i;                    
                }
            }
        }

        private void FillQueue()
        {
            foreach (var request in InputBuffer.GetAllRequest())
            {
                if (MaxQueueLength == 0 || RequestsQueue.Count < MaxQueueLength)
                {
                    Enqueue(request);
                }
                else
                {
                    UnServedBuffer.Add(request);
                }
            }
            InputBuffer.Clear();
        }

        public void Service()
        {
            FillQueue();
            ServiceRequests();
        }

        public override void Initialize()
        {
            RequestsQueue.Clear();
            InputBuffer.Clear();
            UnServedBuffer.Clear();
            WorkingTime = 0;
            DownTime = 0;
            InputBuffer.OnRequestAdded += CheckBuffer;
        }

        private void CheckBuffer()
        {
            if (!InputBuffer.IsEmtpy())
            {
                InputBuffer.OnRequestAdded -= CheckBuffer;

                var executor = ActionExecutor.GetInstance();
                executor.Add(WorkingTime + DownTime, Service);
            }
        }

        public void PutServed(IRequest request)
        {
            if (OutputBuffer != null)
            {
                OutputBuffer.Add(request);
            }
            if (CheckerBuffer != null)
            {
                CheckerBuffer.Add(request.Clone());
            }
        }

        protected IRequest Dequeue()
        {
            IRequest request = null;
            switch (ServiceDiscipline)
            {
                case ServiceDiscipline.FIFO:
                    request = RequestsQueue[0];
                    break;
                case  ServiceDiscipline.LIFO:
                    request = RequestsQueue[RequestsQueue.Count - 1];
                    break;
                case ServiceDiscipline.RANDOM:
                    var rand = RandomHelper.NextInt(0, RequestsQueue.Count - 1);
                    request = RequestsQueue[rand];
                    break;
            }
            RequestsQueue.Remove(request);
            return request;
        }

        protected void Enqueue(IRequest request)
        {
            RequestsQueue.Add(request);
        }
    }
}
